/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifdef _MSC_VER
#   pragma warning(disable : 4503)
#endif

#include "carina/gl-renderer.hh"
#include "carina/window.hh"

#include "carina/common/exception.hh"

#include <cassert>
#include <sstream>
#include <algorithm>
#include <cstring>

namespace Carina
{
#define BUFFER_OFFSET(offset) reinterpret_cast<GLubyte*>(offset)

GLenum TranslateUsage(VBUsage usage)
{
    switch(usage)
    {
    default: assert(!"Unknown usage");
    case CE_STATIC_DRAW: return GL_STATIC_DRAW;
    case CE_STATIC_READ: return GL_STATIC_READ;
    case CE_STATIC_COPY: return GL_STATIC_COPY;
    case CE_STREAM_DRAW: return GL_STREAM_DRAW;
    case CE_STREAM_READ: return GL_STREAM_READ;
    case CE_STREAM_COPY: return GL_STREAM_COPY;
    case CE_DYNAMIC_DRAW: return GL_DYNAMIC_DRAW;
    case CE_DYNAMIC_READ: return GL_DYNAMIC_READ;
    case CE_DYNAMIC_COPY: return GL_DYNAMIC_COPY;
    }
}

static GLenum TranslateVBType(VBType vb_type)
{
    switch(vb_type)
    {
    default: assert(!"Unknown video buffer bind type"); return (GLenum)0;
    case CE_VERTEX_BUFFER: return GL_ARRAY_BUFFER;
    case CE_INDEX_BUFFER: return GL_ELEMENT_ARRAY_BUFFER;
    }
}

static GLenum TranslateDrawMode(DrawModes mode)
{
    switch(mode)
    {
    default: assert(!"Unknown draw topology"); return (GLenum)0;
    case CE_POINT_LIST: return GL_POINTS;
    case CE_LINE_STRIP: return GL_LINE_STRIP;
    case CE_LINE_LIST: return GL_LINES;
    case CE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP;
    case CE_TRIANGLE_LIST: return GL_TRIANGLES;
    case CE_LINE_STRIP_ADJ: return GL_LINE_STRIP_ADJACENCY;
    case CE_LINE_LIST_ADJ: return GL_LINES_ADJACENCY;
    case CE_TRIANGLE_STRIP_ADJ: return GL_TRIANGLE_STRIP_ADJACENCY;
    case CE_TRIANGLE_LIST_ADJ: return GL_TRIANGLES_ADJACENCY;
    }
}

static void TranslateVarType(VarType vtype, GLsizei& elements, GLenum& type, GLboolean& normalized)
{
    switch(vtype)
    {
    default: assert(!"Unknown variable type"); break;
    case VT_R32F: elements = 1, type = GL_FLOAT, normalized = GL_FALSE; break;
    case VT_RG32F: elements = 2, type = GL_FLOAT, normalized = GL_FALSE; break;
    case VT_RGB32F: elements = 3, type = GL_FLOAT, normalized = GL_FALSE; break;
    case VT_RGBA32F: elements = 4, type = GL_FLOAT, normalized = GL_FALSE; break;
    case VT_R16F: elements = 1, type = GL_HALF_FLOAT, normalized = GL_FALSE; break;
    case VT_RG16F: elements = 2, type = GL_HALF_FLOAT, normalized = GL_FALSE; break;
//  case VT_RGB16F: elements = 3, type = GL_HALF_FLOAT, normalized = GL_FALSE; break;
    case VT_RGBA16F: elements = 4, type = GL_HALF_FLOAT, normalized = GL_FALSE; break;
    case VT_R32: elements = 1, type = GL_INT, normalized = GL_FALSE; break;
    case VT_RG32: elements = 2, type = GL_INT, normalized = GL_FALSE; break;
    case VT_RGB32: elements = 3, type = GL_INT, normalized = GL_FALSE; break;
    case VT_RGBA32: elements = 4, type = GL_INT, normalized = GL_FALSE; break;
    case VT_R16: elements = 1, type = GL_SHORT, normalized = GL_FALSE; break;
    case VT_RG16: elements = 2, type = GL_SHORT, normalized = GL_FALSE; break;
//  case VT_RGB16: elements = 3, type = GL_SHORT, normalized = GL_FALSE; break;
    case VT_RGBA16: elements = 4, type = GL_SHORT, normalized = GL_FALSE; break;
    case VT_R8: elements = 1, type = GL_BYTE, normalized = GL_FALSE; break;
    case VT_RG8: elements = 2, type = GL_BYTE, normalized = GL_FALSE; break;
//  case VT_RGB8: elements = 3, type = GL_BYTE, normalized = GL_FALSE; break;
    case VT_RGBA8: elements = 4, type = GL_BYTE, normalized = GL_FALSE; break;
    case VT_uR32: elements = 1, type = GL_UNSIGNED_INT, normalized = GL_FALSE; break;
    case VT_uRG32: elements = 2, type = GL_UNSIGNED_INT, normalized = GL_FALSE; break;
    case VT_uRGB32: elements = 3, type = GL_UNSIGNED_INT, normalized = GL_FALSE; break;
    case VT_uRGBA32: elements = 4, type = GL_UNSIGNED_INT, normalized = GL_FALSE; break;
    case VT_uR16: elements = 1, type = GL_UNSIGNED_SHORT, normalized = GL_FALSE; break;
    case VT_uRG16: elements = 2, type = GL_UNSIGNED_SHORT, normalized = GL_FALSE; break;
//  case VT_uRGB16: elements = 3, type = GL_UNSIGNED_SHORT, normalized = GL_FALSE; break;
    case VT_uRGBA16: elements = 4, type = GL_UNSIGNED_SHORT, normalized = GL_FALSE; break;
    case VT_uR8: elements = 1, type = GL_UNSIGNED_BYTE, normalized = GL_FALSE; break;
    case VT_uRG8: elements = 2, type = GL_UNSIGNED_BYTE, normalized = GL_FALSE; break;
//  case VT_uRGB8: elements = 3, type = GL_UNSIGNED_BYTE, normalized = GL_FALSE; break;
    case VT_uRGBA8: elements = 4, type = GL_UNSIGNED_BYTE, normalized = GL_FALSE; break;
    case VT_R16_SNORM: elements = 1, type = GL_SHORT, normalized = GL_TRUE; break;
    case VT_RG16_SNORM: elements = 2, type = GL_SHORT, normalized = GL_TRUE; break;
//  case VT_RGB16_SNORM: elements = 3, type = GL_SHORT, normalized = GL_TRUE; break;
    case VT_RGBA16_SNORM: elements = 4, type = GL_SHORT, normalized = GL_TRUE; break;
    case VT_R8_SNORM: elements = 1, type = GL_BYTE, normalized = GL_TRUE; break;
    case VT_RG8_SNORM: elements = 2, type = GL_BYTE, normalized = GL_TRUE; break;
//  case VT_RGB8_SNORM: elements = 3, type = GL_BYTE, normalized = GL_TRUE; break;
    case VT_RGBA8_SNORM: elements = 4, type = GL_BYTE, normalized = GL_TRUE; break;
    case VT_R16_UNORM: elements = 1, type = GL_UNSIGNED_SHORT, normalized = GL_TRUE; break;
    case VT_RG16_UNORM: elements = 2, type = GL_UNSIGNED_SHORT, normalized = GL_TRUE; break;
//  case VT_RGB16_UNORM: elements = 3, type = GL_UNSIGNED_SHORT, normalized = GL_TRUE; break;
    case VT_RGBA16_UNORM: elements = 4, type = GL_UNSIGNED_SHORT, normalized = GL_TRUE; break;
    case VT_R8_UNORM: elements = 1, type = GL_UNSIGNED_BYTE, normalized = GL_TRUE; break;
    case VT_RG8_UNORM: elements = 2, type = GL_UNSIGNED_BYTE, normalized = GL_TRUE; break;
//  case VT_RGB8_UNORM: elements = 3, type = GL_UNSIGNED_BYTE, normalized = GL_TRUE; break;
    case VT_RGBA8_UNORM: elements = 4, type = GL_UNSIGNED_BYTE, normalized = GL_TRUE; break;
    case VT_D16: elements = 1, type = GL_DEPTH_COMPONENT16, normalized = GL_TRUE; break;
    case VT_D24S8: elements = 2, type = GL_UNSIGNED_INT_24_8, normalized = GL_TRUE; break; // GL_DEPTH_STENCIL
    case VT_D32: elements = 1, type = GL_DEPTH_COMPONENT32, normalized = GL_TRUE; break;
    case VT_R10G10B10A2: elements = 4, type = GL_INT_2_10_10_10_REV, normalized = GL_FALSE; break;
    case VT_UINT_R10G10B10A2: elements = 4, type = GL_UNSIGNED_INT_2_10_10_10_REV, normalized = GL_FALSE; break;
    }
}

void TranslateIndicesInfo(IndicesType ind_type, size_t& size, GLenum& type)
{
    switch(ind_type)
    {
    default: assert(!"Unknown indices type"); break;
    case IT_UBYTE: size = sizeof(GLubyte), type = GL_UNSIGNED_BYTE; break;
    case IT_USHORT: size = sizeof(GLushort), type = GL_UNSIGNED_SHORT; break;
    case IT_UINT: size = sizeof(GLuint), type = GL_UNSIGNED_INT; break;
    }
}

GLRenderer::GLRenderer(const WindowImpl& window)
    :   m_Window(window),
        m_DefaultState(make_aligned_shared<GLStateObject>(MiscStateDescription())),
        m_CurrentState(m_DefaultState),
        m_DefaultBlendState(make_aligned_shared<GLBlendStateObject>(BlendStateDescription())),
        m_CurrentBlendState(m_DefaultBlendState),
        m_DefaultDepthStencilState(make_aligned_shared<GLDepthStencilStateObject>(DepthStencilStateDescription())),
        m_CurrentDepthStencilState(m_DefaultDepthStencilState)
{
    m_DefaultState->setup();
    m_DefaultBlendState->setup();
    m_DefaultDepthStencilState->setup();

    m_EnumMap["GL_TEXTURE_MIN_FILTER"] = GL_TEXTURE_MIN_FILTER;
    m_EnumMap["GL_TEXTURE_MAG_FILTER"] = GL_TEXTURE_MAG_FILTER;
    m_EnumMap["GL_NEAREST"] = GL_NEAREST;
    m_EnumMap["GL_LINEAR"] = GL_LINEAR;
    m_EnumMap["GL_NEAREST_MIPMAP_NEAREST"] = GL_NEAREST_MIPMAP_NEAREST;
    m_EnumMap["GL_LINEAR_MIPMAP_NEAREST"] = GL_LINEAR_MIPMAP_NEAREST;
    m_EnumMap["GL_NEAREST_MIPMAP_LINEAR"] = GL_NEAREST_MIPMAP_LINEAR;
    m_EnumMap["GL_LINEAR_MIPMAP_LINEAR"] = GL_LINEAR_MIPMAP_LINEAR;
    m_EnumMap["GL_TEXTURE_WRAP_S"] = GL_TEXTURE_WRAP_S;
    m_EnumMap["GL_TEXTURE_WRAP_T"] = GL_TEXTURE_WRAP_T;
    m_EnumMap["GL_TEXTURE_WRAP_R"] = GL_TEXTURE_WRAP_R;
    m_EnumMap["GL_CLAMP_TO_EDGE"] = GL_CLAMP_TO_EDGE;
    m_EnumMap["GL_CLAMP_TO_BORDER"] = GL_CLAMP_TO_BORDER;
    m_EnumMap["GL_MIRRORED_REPEAT"] = GL_MIRRORED_REPEAT;
    m_EnumMap["GL_REPEAT"] = GL_REPEAT;

    m_PreferredShaderCompiler = make_aligned_shared<GLSLShaderCompiler>(*this);
    m_ShaderCompilers.push_back(m_PreferredShaderCompiler);
}

GLRenderer::~GLRenderer()
{
}

void GLRenderer::setScissorRect(size_t x, size_t y, size_t width, size_t height)
{
    glScissor(x, y, width, height);
}

InputLayoutPtr GLRenderer::createInputLayout(const ShaderProgramPtr& shader, const InputElementArray& layout)
{
    GLInputElementArray arr;
    arr.resize(layout.size());
    for(size_t i = 0; i < layout.size(); ++i)
    {
        // TODO: Reloadable shaders might break it! Make it more robust.
        arr[i].location = static_cast<const GLShaderProgram&>(*shader).getAttribLocation(layout[i].m_Name);
        TranslateVarType(layout[i].m_Type, arr[i].size, arr[i].type, arr[i].normalized);
        arr[i].offset = layout[i].m_Offset;
    }
    return make_aligned_shared<GLInputLayout>(arr);
}

void GLRenderer::setInputLayout(const InputLayoutConstPtr& layout)
{
    m_Layout = static_pointer_cast<const GLInputLayout>(layout);
}

VideoBufferPtr GLRenderer::createVideoBuffer(VBType type, size_t size, const void* data, VBUsage usage)
{
    return make_aligned_shared<GLVideoBuffer>(TranslateVBType(type), size, data, TranslateUsage(usage));
}

void GLRenderer::setVideoBuffer(const VideoBufferPtr& vb, size_t stride, size_t offset)
{
    m_Stride = stride;
    m_Offset = offset;
    if(vb)
        static_cast<const GLVideoBuffer&>(*vb).bind();
    else
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    CHECK_OPENGL();
}

void GLRenderer::setIndexBuffer(const VideoBufferPtr& vb, IndicesType itype, size_t offset)
{
    m_IndicesOffset = offset;
    TranslateIndicesInfo(itype, m_IndiceTypeSize, m_IndiceType);
    if(vb)
        static_cast<const GLVideoBuffer&>(*vb).bind();
    else
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    CHECK_OPENGL();
}

void GLRenderer::resizeViewport(size_t w, size_t h)
{
    glViewport(0, 0, w, h);
}

void GLRenderer::getViewport(size_t& w, size_t& h)
{
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    w = viewport[2];
    h = viewport[3];
}

void GLRenderer::draw(DrawModes mode, size_t first, size_t count)
{
    if(m_Layout)
        m_Layout->setup(m_Prog, m_Stride, m_Offset);
    CHECK_OPENGL();
    glDrawArrays(TranslateDrawMode(mode), first, count);
    CHECK_OPENGL();
}

void GLRenderer::drawIndexed(DrawModes mode, size_t first, size_t count)
{
    if(m_Layout)
        m_Layout->setup(m_Prog, m_Stride, m_Offset);
    CHECK_OPENGL();
    glDrawElements(TranslateDrawMode(mode), count, m_IndiceType, BUFFER_OFFSET((m_IndicesOffset+first)*m_IndiceTypeSize));
    CHECK_OPENGL();
}

ShaderCompilerPtr GLRenderer::getPreferredShaderCompiler(const ShadingLanguagesList& shlang_list)
{
    for(auto& lang : shlang_list)
        if(lang == m_PreferredShaderCompiler->getName())
            return m_PreferredShaderCompiler;
    
    std::stringstream ss;
    for(auto& lang : shlang_list)
        ss << lang << ", ";
    THROW_EXCEPTION("Unsupported shading languages: " + ss.str());
}

void GLRenderer::setPreferredShadingLanguage(const string& name)
{
    auto i = std::find_if(m_ShaderCompilers.begin(), m_ShaderCompilers.end(), [&name](const ShaderCompilerPtr& sc) { return sc->getName() == name; });
    if(i == m_ShaderCompilers.end())
        THROW_EXCEPTION("Unknown shader compiler: " + name);
    m_PreferredShaderCompiler = *i;
}

ShaderCompilerPtr GLRenderer::getShaderCompiter(const string& shading_language)
{
    auto i = std::find_if(m_ShaderCompilers.begin(), m_ShaderCompilers.end(), [&shading_language](const ShaderCompilerPtr& sc) { return sc->getName() == shading_language; });
    if(i == m_ShaderCompilers.end())
        THROW_EXCEPTION("Unknown shader compiler: " + shading_language);
    return *i;
}

void GLRenderer::useShaderProgram(const ShaderProgramPtr& prog)
{
    m_Prog = prog;
    if(m_Prog)
        static_cast<GLShaderProgram&>(*m_Prog).use();
    else
        glUseProgram(0);
}

TexturePtr GLRenderer::createTexture(const TextureDescription& desc, const void* data, int flags)
{
    return make_aligned_shared<GLTexture>(desc, data, flags);
}

StateObjectPtr GLRenderer::createStateObject(const MiscStateDescription& desc)
{
    return make_aligned_shared<GLStateObject>(desc);
}

void GLRenderer::setStateObject(const StateObjectPtr& state_obj)
{
    m_CurrentState = static_pointer_cast<GLStateObject>(state_obj);
    m_CurrentState->setup();
}

StateObjectPtr GLRenderer::getStateObject()
{
    return m_CurrentState;
}

BlendStateObjectPtr GLRenderer::createBlendStateObject(const BlendStateDescription& desc)
{
    return make_aligned_shared<GLBlendStateObject>(desc);
}

void GLRenderer::setBlendStateObject(const BlendStateObjectPtr& bstate_obj)
{
    m_CurrentBlendState = static_pointer_cast<GLBlendStateObject>(bstate_obj);
    if(bstate_obj.get())
        m_CurrentBlendState->setup();
    else
        m_DefaultBlendState->setup();
}

BlendStateObjectPtr GLRenderer::getBlendStateObject()
{
    return m_CurrentBlendState;
}

DepthStencilStateObjectPtr GLRenderer::createDepthStencilStateObject(const DepthStencilStateDescription& desc)
{
    return make_aligned_shared<GLDepthStencilStateObject>(desc);
}

void GLRenderer::setDepthStencilStateObject(const DepthStencilStateObjectPtr& dsstate_obj)
{
    m_CurrentDepthStencilState = static_pointer_cast<GLDepthStencilStateObject>(dsstate_obj);
    m_CurrentDepthStencilState->setup();
}

DepthStencilStateObjectPtr GLRenderer::getDepthStencilStateObject()
{
    return m_CurrentDepthStencilState;
}

void GLRenderer::setClearColor(const Vector4& c)
{
    glClearColor(c.x(), c.y(), c.z(), c.w());
}

void GLRenderer::clearColorBuffer(size_t idx, const Vector4& color)
{
    glClearBufferfv(GL_COLOR, idx, color.elem);
    CHECK_OPENGL();
}

void GLRenderer::clearDepthStencilBuffer(float depth, int stencil)
{
    glClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil);
}

Vector4 GLRenderer::getClearColor() const
{
    Vector4 color;
    glGetFloatv(GL_COLOR_CLEAR_VALUE, color.elem);
    return color;
}

void GLRenderer::clearAll()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    CHECK_OPENGL();
}

Matrix4 GLRenderer::createPerspectiveMatrix(float fovy, float aspect, float zNear, float zFar)
{
    float f = 1.0f / tan((fovy / 360.0f) * math_pi);
    return Matrix4(f / aspect,  0.0f,   0.0f,                           0.0f,
                   0.0f,        f,      0.0f,                           0.0f,
                   0.0f,        0.0f,   (zNear + zFar)/(zNear - zFar), -1.0f,
                   0.0f,        0.0f,   (2*zNear*zFar)/(zNear - zFar),  0.0f);
}

Matrix4 GLRenderer::createOrthoMatrix(float left, float right, float bottom, float top, float _near, float _far)
{
    return Matrix4(2.0f/(right-left),           0.0f,                       0.0f,                       0.0f,
                   0.0f,                        2.0f/(top-bottom),          0.0f,                       0.0f,
                   0.0f,                        0.0f,                      -2.0f/(_far-_near),          0.0f,
                   (right+left)/(left-right),   (top+bottom)/(bottom-top),  (_far+_near)/(_near-_far),  1.0f);
}

void GLRenderer::requires(const char* ext)
{
    if(!strstr(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)), ext))
        THROW_EXCEPTION("The following extension is not available: " + std::string(ext));
}

GLenum GLRenderer::getEnum(const string& name)
{
    EnumMap::iterator i = m_EnumMap.find(name);
    if(i == m_EnumMap.end())
        THROW_EXCEPTION("unknown enum: " + name);
    return i->second;
}

FramebufferPtr GLRenderer::createFramebuffer()
{
    return make_aligned_shared<GLFramebuffer>();
}

void GLRenderer::bindFramebuffer(const FramebufferPtr& fb)
{
    m_ReadFramebuffer = m_DrawFramebuffer = static_pointer_cast<GLFramebuffer>(fb);
    if(fb)
        static_cast<GLFramebuffer&>(*fb).bind();
    else
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glDrawBuffer(GL_BACK);
    }
}

void GLRenderer::bindDrawFramebuffer(const FramebufferPtr& fb)
{
    m_DrawFramebuffer = static_pointer_cast<GLFramebuffer>(fb);
    if(fb)
        static_cast<GLFramebuffer&>(*fb).bindDraw();
    else
    {   
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glDrawBuffer(GL_BACK);
    }
}

void GLRenderer::bindReadFramebuffer(const FramebufferPtr& fb)
{
    m_ReadFramebuffer = static_pointer_cast<GLFramebuffer>(fb);
    if(fb)
        static_cast<GLFramebuffer&>(*fb).bindRead();
    else
        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}

FramebufferPtr GLRenderer::getDrawFramebuffer()
{
    return m_DrawFramebuffer;
}

FramebufferPtr GLRenderer::getReadFramebuffer()
{
    return m_ReadFramebuffer;
}

// TODO: multiple possible read buffers
void GLRenderer::blitFramebuffer(int buffers)
{
    GLint   fb1_w, fb1_h,
            fb2_w, fb2_h;
    GLbitfield  mask = 0;
    assert(buffers);
    if(buffers & CE_DEPTH_BUFFER_BIT) 
        mask |= GL_DEPTH_BUFFER_BIT;
    if(buffers & CE_COLOR_BUFFER_BIT) 
        mask |= GL_COLOR_BUFFER_BIT;

    if(m_ReadFramebuffer.get())
    {
        FBAttachmentInfo& att = m_ReadFramebuffer->getColorAttachment(0);
        GLTexture& tex = static_cast<GLTexture&>(*att.texture);
        tex.bind();
        fb1_w = tex.getWidth(att.level);
        fb1_h = tex.getHeight(att.level);
    }
    else
    {
        fb1_w = m_Window.getWidth();
        fb1_h = m_Window.getHeight();
    }

    if(m_DrawFramebuffer.get())
    {
        FBAttachmentInfo& att = m_DrawFramebuffer->getColorAttachment(0);
        GLTexture& tex = static_cast<GLTexture&>(*att.texture);
        tex.bind();
        fb2_w = tex.getWidth(att.level);
        fb2_h = tex.getHeight(att.level);
    }
    else
    {
        fb2_w = m_Window.getWidth();
        fb2_h = m_Window.getHeight();
    }

    assert(fb1_w == fb2_w && fb1_h == fb2_h);

    glBlitFramebuffer(0, 0, fb1_w, fb1_h, 
                      0, 0, fb2_w, fb2_h, 
                      mask, GL_NEAREST);
    CHECK_OPENGL();
}
}
